<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>QO-100 WebSDR</title>
        <script src="jquery.min.js"></script>
        <script src="pcmplayer.js"></script>
        <script src="coltab.js"></script>
        <script src="bandplan.js"></script>
        <script src="config.js"></script>
<style>

body { 
  font-family: Helvetica, Arial, Geneva, sans-serif;
}

.leftside
{
    position: relative;
    width: 620px;
    height: 150px;
    background-color: #f0f0f0;
}

.rl_space
{
    width: 10px;
    background-color: #ffffff;
}

.midside
{
    position: relative;
    width: 560px;
    height: 150px;
    background-color: #f0f0f0;
}

.rightside
{
    position: relative;
    width: 300px;
    height: 150px;
    background-color: #f0f0f0;
}

.leftrow_menuline
{
    background-color: #e0e0e0;
    font-size:16px;
    font-weight: 500;
    width: 620px;
    display: flex;
    position: absolute;
    bottom: 0px;
}

.myflex
{
    display: flex;
}

.midrow_menuline
{
    font-size:16px;
    font-weight: 500;
    width: 560px;
    display: flex;
    position: absolute;
    bottom: 0px;
}

.rightrow_menuline
{
    font-size:16px;
    font-weight: 500;
    width: 300px;
    display: flex;
    position: absolute;
    bottom: 0px;
}

.rightrow
{
    font-size:20px;
    font-weight: 500;
    width: 570px;
    display: flex;
}

.rightrow_space
{
    width: 570px;
    height: 10px;
}

.rightcell_label
{
    width: 100px;
}

.rightcell_label_header
{
    width: 570px;
    font-weight: 600;
}

.rightcell_slider
{
    width: 185px;
}

.filtercontainer
{
    display: flex;
    align-items: center;
    height: 36px;
}

.audio_filter_slider
{
    width: 170px;
}

.audiolabel
{
    text-align: center;
    padding-left: 1em;
    font-weight: 200;
}

.controls 
{
    width: 1500px;
    display: flex;
    background-color: #e0e0e0;
}

.controls_left
{
    width: 630px;
    display: flex;
}

.controls_left_left
{
    font-size:16px;
    width: 150px;
    color: blue;
    font-weight: 600;
}

.controls_left_mid
{
    font-size:16px;
    width: 200px;
    color: blue;
    font-weight: 600;
}

.controls_left_right
{
    font-size:14px;
    width: 280px;
    color: blue;
    font-weight: 600;
}

.header_mytitle
{
    font-size:30px;
    color: white;
    background-color:#808080;
    width: 1500px;
    text-align: center;
    font-weight: 900;
}

.header_subtitle
{
    font-size:20px;
    color: white;
    background-color:#808080;
    width: 1500px;
    text-align: center;
}

.tuneinfo
{
    font: "Arial";
    font-size:26px;
    font-weight: 600;
    color: blue;
    width: 1500px;
    text-align: center;
}

.selectbox {
    width: 120px;
    padding:6px;
    margin-top: 6px;
    -webkit-border-radius:4px;
    -moz-border-radius:4px;
    border-radius:4px;
    background: #e8e8e8;
    color:#404040;
    outline:none;
    display: inline-block;
    -webkit-appearance:none;
    -moz-appearance:none;
    appearance:none;
    cursor:pointer;
    font-size: 16px;
    font-weight: 600;
}

.mybutton {
    width:99px;
    padding: 1px;
    margin-top: 3px;
    margin-bottom: 3px;
    -webkit-border-radius:4px;
    -moz-border-radius:4px;
    border-radius:4px;
    background: #c8c8e8;
    color:#404040;
    outline:none;
    display: inline-block;
    -webkit-appearance:none;
    -moz-appearance:none;
    appearance:none;
    cursor:pointer;
    font-size: 16px;
    font-weight: 600;
}

.qrg {
    width:100px;
    font-size: 12px;
    
    box-sizing: border-box;
    height: 18px;
    line-height: 12px;
    padding: 2px;
    border: 1px solid gray;
    border-radius: 2px;
}

.measurements {
    font-family:  "Lucida Console", Monaco, monospace;
    font-size: 14px;
    font-weight: 200;
    color: blue;
}

.cat_message {
    font: "Courier New";
    font-size:12px;
    font-weight: 200;
    color: red;
}

canvas {
    display:block;
    margin: -2px;
}

</style>

<script type="text/javascript">
    
    window.onload = onstart;
    window.onbeforeunload = closingCode;
    
    var intv;
    var interval;
    var sockintv;
    var sockOpen = 0;
    var websocket;

    var tunerqrg = 0;
    var end = 0;
    var tuneqrg = 0;
    var foffset = 0;
    var resolution = 1;
    var vertlines = 1;
    var vertmarker = 1;
    var ssbmode = 0;
    
    var end1 = 0;    
    
    var tuneqrg1 = 0;
    var foffset1 = 0;
    var resolution1 = 1;
    
    var player;
    var playON = 0;

    
    function MouseDownHandler(e)
    {
        var e = window.event || e; // old IE support
        var mval = tuneqrg/1e3 + (e.pageX * resolution)/1e6;
        mval -= 0.001200;   // needed to get right value from mouse pointer, reason unknown
        mval *= 1000000;    // to Hz
        
        if(e.which == 1)    // left click
            websocket.send("mousepo:"+ mval.toFixed(0) + "\0");
        
        if(e.which == 3)    // right click
            websocket.send("seticom:"+ mval.toFixed(0) + "\0");
    }

    function MouseMoveHandler(e)
    {
        var e = window.event || e; // old IE support
        var mval = tuneqrg/1e3 + (e.pageX * resolution)/1e6;
        mval -= 0.001200; // needed to get right value from mouse pointer, reason unknown
        
        $('#mousepos').html("mouse X: " + mval.toFixed(6) + " MHz<br>mouse Y: " + "---" + " dB");
    }
    
    function MouseMoveHandler_spek(e)
    {
        var e = window.event || e; // old IE support
        var mval = tuneqrg/1e3 + (e.pageX * resolution)/1e6;
        mval -= 0.001200; // needed to get right value from mouse pointer, reason unknown
        
        var y = e.offsetY;
        var dbm = scale_y_to_dbm(y);
        
        $('#mousepos').html("mouse X: " + mval.toFixed(6) + " MHz<br>mouse Y: " + dbm.toFixed(1) + " dB");
    }
    
    function MouseWheelHandler(e)
    {
        // cross-browser wheel delta
        var e = window.event || e; // old IE support
        var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
        
        if(e.shiftKey == true)
            delta *= 25;
        
        websocket.send("mousewh:"+ delta + "\0");

        return false;
    }
    
    function audioON()
    {
        if(playON == 0)
        {
            player = new PCMPlayer({
                    encoding: '16bitInt',
                    channels: 1,
                    sampleRate: 8000,
                    flushingTime: 500
            });
            
            playON = 1;
            $("#audiobutton").html('AUDIO ON');
        }
        else if(playON == 1)
        {
            playON = 2;
            $("#audiobutton").html('AUDIO OFF');
            if(websocket != undefined)
                websocket.send("audioon:0\0");
        }
        else if(playON == 2)
        {
            playON = 1;
            $("#audiobutton").html('AUDIO ON');
            if(websocket != undefined)
                websocket.send("audioon:1\0");
        }
    }
    
    var filter_cutoff = 2800;
    var filter_cutoff_low = 200;
    
    function onstart()
    {
        // activate the PCMplayer
        audioON();
        audioON();  // and switch audio initially off
        
        setListboxes();
        
        // click into WF
        var myitem = document.getElementById("wf_overlay");
        if (myitem.addEventListener)
        {
            myitem.addEventListener("mousedown", MouseDownHandler, false);
        }
        
        // disable contect menu on right clock
        $('#wf_overlay').bind('contextmenu', function(e) {
            return false;
        });
        
        // mouse in Spec
        var myitem = document.getElementById("spec");
        if (myitem.addEventListener)
        {
            myitem.addEventListener("mousemove", MouseMoveHandler_spek, false);
        }
        
        // mouse wheel in WF
        var myitem = document.getElementById("wf_overlay");
        if (myitem.addEventListener)
        {
            // IE9, Chrome, Safari, Opera
            myitem.addEventListener("mousewheel", MouseWheelHandler, false);
            // Firefox
            myitem.addEventListener("DOMMouseScroll", MouseWheelHandler, false);
            
            myitem.addEventListener("mousemove", MouseMoveHandler, false);
        }
        // IE 6/7/8
        else
        {
            myitem.attachEvent("onmousewheel", MouseWheelHandler);
        }
        
        // click into lower WF
        document.getElementById('wf1_overly').addEventListener
        (/*'click',
            function(evt){
                var xpos = evt.clientX;
                var rect = document.getElementById("wf_overlay").getBoundingClientRect();
                websocket.send("mouselo:"+ (xpos - rect.left) + "\0");
            },false*/
            
            'mousedown',
            function(evt)
            {
                var xpos = evt.clientX;
                var rect = document.getElementById("wf_overlay").getBoundingClientRect();
                if(evt.which == 1) // left click
                    websocket.send("mouselo:"+ (xpos - rect.left) + "\0");
                if(evt.which == 3) // right click
                    websocket.send("seticlo:"+ (xpos - rect.left) + "\0");
            },false
        );
        
        // disable contect menu on right clock
        $('#wf1_overly').bind('contextmenu', function(e) {
            return false;
        });

        
        // mouse wheel in lower WF
        var myitem = document.getElementById("wf1_overly");
        if (myitem.addEventListener)
        {
            // IE9, Chrome, Safari, Opera
            myitem.addEventListener("mousewheel", MouseWheelHandler, false);
            // Firefox
            myitem.addEventListener("DOMMouseScroll", MouseWheelHandler, false);
        }
        // IE 6/7/8
        else
        {
            myitem.attachEvent("onmousewheel", MouseWheelHandler);
        }

        sockintv = setInterval(checkSocket, 1000);
    }
    
    var reload = 0;
    
    function reloadData()
    {
        reload = 1;
    }
    
    var intervalset = 0;
    
    function checkSocket()
    {
        if(sockOpen == 0)
        {
            if(websocket != null)
                websocketclose();
            openWebSocket();
        }
        
        showLock();
        
        // set interval to 5s, this ensures that it can reconnect 
        // if the server was down. This does not work with faster intervals
        if(intervalset == 0)
        {
            intervalset = 1;
            clearInterval(sockintv);
            sockintv = setInterval(checkSocket, 5000);
        }
    }
    
    function websocketclose()
    {
        if(websocket != null)
        {
            //console.log("close websocket");
            websocket.close();
            websocket = null;
        }
        else
        {
            //console.log("close websocket, already closed");
        }
    }

    var pix = new Array();
    var pixwidth;
    
    var pix1 = new Array();
    var pixwidth1;
    
    var spectrum_picture_height;
    
    Number.prototype.pad = function(size) 
    {
        var s = String(this);
        while (s.length < (size || 2)) {s = "0" + s;}
        return s;
    }
    function drawWFtitle_eshail_rx(id)
    {
        var cnv = document.getElementById(id); 
        var ctx = cnv.getContext("2d");
        var width = cnv.width;
        var height = cnv.height;
        
        ctx.font = "14px Arial";
        ctx.fillStyle = "#0000c0";
        ctx.fillRect(0,0,width,height);
        
        ctx.fillStyle = "#ffffff";
        ctx.fillText("10489.",0,16);
        
        for(var i=1; i<qo100_bp.length; i++)
        {
            var qrg = qo100_bp[i]['from'] - 10489000;
            var x = cpos(qo100_bp[i]['from']);
            ctx.fillText(qrg.pad(3),x-10,16);
        }
    }
    
    function drawWFtitle_eshail_tx(id)
    {
        var cnv = document.getElementById(id); 
        var ctx = cnv.getContext("2d");
        var width = cnv.width;
        var height = cnv.height;
        
        ctx.font = "14px Arial";
        ctx.fillStyle = "#0000c0";
        ctx.fillRect(0,0,width,height);
        
        ctx.fillStyle = "#ffffff";
        ctx.fillText("2400.",0,16);
        
        for(var i=1; i<qo100_bp.length; i++)
        {
            var qrg = qo100_bp[i]['from'] - 10489500;
            var x = cpos(qo100_bp[i]['from']);
            ctx.fillText(qrg.pad(3),x-10,16);
        }
    }
    
    function drawWFtitle_eshail_ssb(id,qrg)
    {
        var cnv = document.getElementById(id); 
        var ctx = cnv.getContext("2d");
        var width = cnv.width;
        var height = cnv.height;
        
        ctx.font = "16px Arial";
        ctx.fillStyle = "#0000c0";
        ctx.fillRect(0,0,width,height);
        
        ctx.fillStyle = "#ffffff";
        
        ctx.textAlign = "right";
        ctx.fillText((qrg+0.0075).toFixed(6),width-1,16);
        
        ctx.textAlign = "left";
        ctx.fillText((qrg-0.0075).toFixed(6),0,16);

        ctx.textAlign = "center";
        ctx.fillText(qrg.toFixed(6),width/2,16);
    }
    
    function drawMODEtitle(id,fstart, fend, resolution)
    {
        var cnv = document.getElementById(id); 
        var ctx = cnv.getContext("2d");
        var width = cnv.width;
        var height = cnv.height;

        ctx.font = "16px Arial";
        ctx.fillStyle = "#404040";
        ctx.fillRect(0,0,width,height);
        
        for(var i=0; i<qo100_bp.length; i++)
        {
            ctx.fillStyle = qo100_bp[i]['bgcol'];
            var x1 = cpos(qo100_bp[i]['from']);
            var x2 = cpos(qo100_bp[i]['to']);
            ctx.fillRect(x1,0,x2-x1,20);
            
            ctx.fillStyle = qo100_bp[i]['txtcol'];
            ctx.fillText(qo100_bp[i]['name'],cpos(qo100_bp[i]['txtpos']),15);
        }
    }
    
    var wfcolor = "red";
    var linethickness = 1;
    var next_linethickness = 1;
    var sockurl = '?';
    
    var specarrlen = 8;
    var midarr = new Array(200);
    var peakarr;
    var levelarr = new Array(2000);
    var arrfirst = 1;
    var speclevel = -180;
    var specgain = -60;
    var audiogain = 1;
    var wflevel = 230;
    var wfgain = 50;
    var bcnarrlen = 500;
    var beaconarr = new Array(bcnarrlen);
    var bcnarrempty = 1;
    var maxval = -999999;
    
    function draw_spec()
    {
        // get the elemt IDs and dimensions
        var canvas = document.getElementById("spec"); 
        var context = canvas.getContext("2d");
        var width = canvas.width;
        var height = canvas.height;
        spectrum_picture_height = height;
        
        // variables to store min/max values for this single path
        var n_maxval = -999999;
        var beacon_maxval = -999999;
        var beacon_mid_maxval = -999999;
        
        // variable to calculate lowest 0dB position
        var mittel = 0;
        
        // NB-Transponder, position of beacons
        var xm1 = cpos(10489497);
        var xm2 = cpos(10489503);
        var xm3 = cpos(10489997);
        var xm4 = cpos(10490003);

        // storage for the dB value for each horizontal pixel
        // used for dB label
        var pntarr = new Array(width);
        
        // draw background
        context.fillStyle = '#404040';
        context.fillRect(0,0,width,height);
        
        // set colors of spectrum lines and fill
        context.strokeStyle = '#ffffff';
        context.fillStyle = '#c0c0c0';
        
        // midarr is used to calculate the mean value according
        // to the Average-Slider setting
        // generate and initialize the array
        if(arrfirst == 1)
        {
            peakarr = new Array(width);
            for(var x=0; x<width; x++)
            {
                peakarr[x] = -200;
            }
            
            for(var i=0; i<200; i++)
            {
                midarr[i] = new Array(width);
                for(var x=0; x<width; x++)
                {
                    midarr[i][x] = -pix[x];
                }
            }
            arrfirst = 0;
        }

        // draw spectrum as a filled area
        // start drawing in left-bottom corner
        context.beginPath();
        context.moveTo(0, height-1);

        // loop through all horizontal pixels
        for(var x=0; x<width; x++)
        {
            if(trace_mode == 0)
            {
                // Averaging mode
                // calculate the average value, first
                // shift the array and put new data at the beginning
                for(var idx=(specarrlen-1); idx>=1; idx--)
                    midarr[idx][x] = midarr[idx-1][x];
                // add new value
                midarr[0][x] = -pix[x];
                
                // calculate the average
                var mv = 0; // mid value
                for(var idx=0; idx<specarrlen; idx++)
                    mv += midarr[idx][x];
                mv /= specarrlen;
            }
            
            if(trace_mode == 1)
            {
                // Peak mode
                // use only the first cell [0] of midarr
                // put the maximum value here
                var newval = -pix[x];
                if(newval > peakarr[x])
                    peakarr[x] = newval;
                
                mv = peakarr[x];
            }
            // mv is now the dB value of the signal at position x
            
            // find the highest value, excluding the beacons
            // which is the highest user level (n_maxval)
            if(mv > n_maxval && (x < xm1 || x > xm4 || (x > xm2 && x < xm3)))
                n_maxval = mv;

            // find the highest value of the beacons
            if(mv > beacon_maxval && ((x > xm1 && x < xm2) || (x > xm3 && x < xm4)))
                beacon_maxval = mv;
                
            // sum all values for the mean value calculation
            mittel += mv;
            
            // draw the spectrum line
            context.lineTo(x,scale(mv));

            // add value to end of array (used for dB-Labels)
            pntarr[x] = mv;
        }
        
        // mean value calculation
        // in the NB transponder this value is a little bit over the noise level
        mittel /= width;

        // finish the spectrum display by closing and filling the area
        context.lineTo(width-1,height-1);
        context.fill();
        context.stroke();
        
        // take the max value
        maxval = n_maxval;
        
        // now the spectrum is drawn, make the nice features, lables ...

        // maximum allowed power level marker
        // draw a red line at highest beacon level of the last bcnarrlen measurements
        if(beacon_maxval > -200)    // only is valid values are available
        {
            // the beacon level must be stored for a longer time (see bcnarrlen)
            // to ignore fading and CW, just find the maximum values
            if(bcnarrempty == 1)
            {
                bcnarrempty = 0;
                // init beaconarr
                for(var d=0; d<bcnarrlen; d++)
                    beaconarr[d] = beacon_maxval;
            }
            else
            {
                // store beacon levels in an array
                for(var d=(bcnarrlen-1); d>=1; d--)
                    beaconarr[d] = beaconarr[d-1];
                beaconarr[0] = beacon_maxval;
            }

            // find the maximum beacon level, which is the max. allowed power level
            beacon_mid_maxval = -999999;
            for(var d=0; d<bcnarrlen; d++)
            {
                if(beaconarr[d] > beacon_mid_maxval)
                    beacon_mid_maxval = beaconarr[d];
            }

            // ybcn is the Y-pixel position of the beacon level
            ybcn = scale(beacon_mid_maxval);

            // double hor red line at beacon value
            context.strokeStyle = '#ff0000';
            context.beginPath();
            context.moveTo(0, ybcn);
            context.lineTo(width-1, ybcn);
            context.stroke();

            context.strokeStyle = '#ff0000';
            context.beginPath();
            context.moveTo(0, ybcn-1);
            context.lineTo(width-1, ybcn-1);
            context.stroke();

            // label for beacon level
            var dynrange = beacon_mid_maxval - basenoiselevel;
            context.font = "12px Arial";
            context.fillStyle = "#ffc0c0";
            context.fillText("+" + dynrange.toFixed(0) + " dB",10,ybcn+10);
            context.fillText("max. beacon level",10,ybcn-5);
        }
        
        // hor line at highest value, except the beacon
        // highest user level
        context.strokeStyle = '#008080';
        context.beginPath();
        context.moveTo(0, scale(maxval));
        context.lineTo(width-1, scale(maxval));
        context.stroke();

        // write dB value to max line
        var dynrange = maxval - basenoiselevel;
        context.font = "12px Arial";
        context.fillStyle = "#ffc0c0";
        context.fillText("+" + dynrange.toFixed(0) + " dB",10,scale(maxval)+10);

        // hor line at lowest value (between mid value and lowest value)
        // the is the bottom noise floor level
        var yvalue = basenoiselevel+(mittel-basenoiselevel)*3/4;
        context.strokeStyle = '#80ff80';
        context.beginPath();
        context.moveTo(0, scale(yvalue));
        context.lineTo(width-1, scale(yvalue));
        context.stroke();

        // write dB value to every peak which is > 15dB over min
        // draw max. 20 peak labels
        var maxv = 0;
        var maxx = 0;
        for(var pk=0; pk<20; pk++)
        {
            maxv = 0;
            maxx = 0;
            for(var x=10; x<(width-10); x++) // not at the margins
            {
                var dval = pntarr[x] - basenoiselevel;
                // seek max. value
                if(dval > maxv) 
                {
                    maxv = dval;
                    maxx = x;
                }
            }
            if(maxv < 15) break;	// only values >15dB over basenoiselevel
            // max value is at position maxx
            // draw the label
            context.fillText(maxv.toFixed(0) + " dB",maxx,scale(pntarr[maxx]));
            // clear the values +-10 of this max value
            for(var c = (maxx-10); c < (maxx+10); c++)
            {
                if(c>=0 && c<width)
                    pntarr[c] = basenoiselevel;
            }
        }

        // vertical lines for QO-100 mode ranges
        if(vertlines == 1)
        {
            for(var i=1; i<qo100_bp.length; i++)
                vertline(qo100_bp[i]['from']);
        }
        
        // the spectrum display is drawn
        
        // do the calculations for the automatic level and gain settings
        var ckb = $("#spekautolevel").is(':checked');
        if(ckb && basenoiselevel < 0)    // ignore invalid values
        {
            // calculate speclevel and specgain so that
            // basenoiselevel goes to y=(height-5) and maxval goes to y=15
            var ymin = 145; // min/max positions in screen coordinates
            var ymax = 15;
            var maxv = maxval;
            if(beacon_mid_maxval > maxval)
                maxv = beacon_mid_maxval;
            var a = (height - ymin)/height;
            var b = (height - ymax)/height;
            specgain =(basenoiselevel*(b-1)-maxv*(a-1))/(a*(b-1)-b*(a-1));
            speclevel = (a*specgain-basenoiselevel)/(a-1);
            
            // adjust WF parameters
            wflevel = 230;
            wfgain = 50;

            //console.log("speclevel:"+speclevel+" basenoiselevel:"+basenoiselevel + "specgain:"+specgain+" maxval:"+maxval);
            
            var slider_speclevel = document.getElementById("speclevel");
            slider_speclevel.value = speclevel;
            
            var slider_specgain = document.getElementById("specgain");
            slider_specgain.value = specgain;
            
            var slider_wflevel = document.getElementById("wflevel");
            slider_wflevel.value = wflevel;
            
            var slider_wfgain = document.getElementById("wfgain");
            slider_wfgain.value = wfgain;
        }

        // helper functions
        
        // scale dBm value to y-pixelpos
        function scale(v)
        {
            var y = -height*(v-speclevel)/(specgain-speclevel) + height;
            return y;
        }
        
        function vertline(qrg)
        {
            var x = cpos(qrg);
        
            context.strokeStyle = '#4040c0';
            context.beginPath();
            context.moveTo(x, 0);
            context.lineTo(x, height-1);
            context.stroke();
        }
    }
    
    // scale y-pixelpos to dBm value over noise (basenoiselevel)
    function scale_y_to_dbm(y)
    {
        var dbm = (specgain-speclevel)*(spectrum_picture_height-y)/spectrum_picture_height + speclevel;
        return dbm - basenoiselevel;
    }

    var arrmidlen0 = 20;
    var cmaxvalarr0 = new Array(arrmidlen0);
    var cminvalarr0 = new Array(arrmidlen0);
    var cmaxval0;
    var cminval0;

    var hWFrefval0 = 50;     // shifts the black value (higher=darker)
    var hWFgain0 = 0.00001;   // contrast (must be > 0, lower value = higher contrast)

    var minlog0;
    var factor0;
    var frs0 = 1;

    function calcColorParms(d, len)
    {
        // find min/max values in one pixel line
        // calc the average value to avoid flickering
        var max = -9999;
        var min = 9999;
        
        for(var i=0; i<len; i++)
        {
            // d[i] is the dBm value at the antenna input (if calibrated and AGC off)
            // it is a negative value, max range: -180 ... 0
            if(d[i] < min) min = d[i];
            if(d[i] > max) max = d[i];
        }
        
        if(frs0)
        {
            // init array
            frs0=0;
            for(var i=0; i<arrmidlen0; i++)
            {
                cminvalarr0[i] = min;
                cmaxvalarr0[i] = max;
            }
            
            // prepare values used by getPixelColor
            factor0 = hWFgain0/10;
            minlog0 = Math.log(factor0 + 1) / 255;
        }
        
        // insert into mid array
        for(var i=(arrmidlen0-1); i>=1; i--)
        {
            cminvalarr0[i] = cminvalarr0[i-1];
            cmaxvalarr0[i] = cmaxvalarr0[i-1];
        }
        cminvalarr0[0] = min;
        cmaxvalarr0[0] = max;
        
        cminval0  = 0;
        cmaxval0 = 0;
        for(var i=0; i<arrmidlen0; i++)
        {
            cminval0 += cminvalarr0[i];
            cmaxval0 += cmaxvalarr0[i];
        }
        cminval0 /= arrmidlen0;
        cmaxval0 /= arrmidlen0;
        
        cminval0 = -cminval0;
        cmaxval0 = -cmaxval0;
        
        //console.log("cminval:" + cminval + " cmaxval:" + cmaxval);
    }
    
    function getPixelColor(val)
    {
        // === black value, no signal, background noise ===
        // shift the value up, so that cminval is color=0
        val = -val;
        val -= cminval0;
        val -= (hWFrefval0/10);
        
        // === max level, strongest signal ===
        // make the loudest signal to 255
        // shift cmaxval the same factor as the signal value, so both start at 0
        var cmax = (cmaxval0 - cminval0);
        // expand the signal value so that the maxval is 255
        // do this with a log formular, high values are more effected
        var lin = val * factor0 / cmax;
        var revlin = factor0 - lin + 1;
        var mylog = Math.log(revlin);
        var logTo255 = mylog/ minlog0;
        //var val1 = 255 - logTo255;
        var val1 = logTo255;
        
        // low value (<=2) colors reserved for other purposes
        if (val1 <= 2) val1 = 3;
        if (val1 > 250) val1 = 255;
        return val1;
    }

    var arrmidlen1 = 20;
    var cmaxvalarr1 = new Array(arrmidlen1);
    var cminvalarr1 = new Array(arrmidlen1);
    var cmaxval1;
    var cminval1;

    var hWFrefval1 = 300;     // shifts the black value (higher=darker)
    var hWFgain1 = 0.00001;   // contrast (must be > 0, lower value = higher contrast)

    var minlog1;
    var factor1;
    var frs1 = 1;

    function calcColorParms1(d, len)
    {
        // find min/max values in one pixel line
        // calc the average value to avoid flickering
        var max = -9999;
        var min = 9999;
        
        for(var i=0; i<len; i++)
        {
            // d[i] is the dBm value at the antenna input (if calibrated and AGC off)
            // it is a negative value, max range: -180 ... 0
            if(d[i] < min) min = d[i];
            if(d[i] > max) max = d[i];
        }
        
        if(frs1)
        {
            // init array
            frs1=0;
            for(var i=0; i<arrmidlen1; i++)
            {
                cminvalarr1[i] = min;
                cmaxvalarr1[i] = max;
            }
            
            // prepare values used by getPixelColor
            factor1 = hWFgain1/10;
            minlog1 = Math.log(factor1 + 1) / 255;
        }
        
        // insert into mid array
        for(var i=(arrmidlen1-1); i>=1; i--)
        {
            cminvalarr1[i] = cminvalarr1[i-1];
            cmaxvalarr1[i] = cmaxvalarr1[i-1];
        }
        cminvalarr1[0] = min;
        cmaxvalarr1[0] = max;
        
        cminval1  = 0;
        cmaxval1 = 0;
        for(var i=0; i<arrmidlen1; i++)
        {
            cminval1 += cminvalarr1[i];
            cmaxval1 += cmaxvalarr1[i];
        }
        cminval1 /= arrmidlen1;
        cmaxval1 /= arrmidlen1;
        
        cminval1 = -cminval1;
        cmaxval1 = -cmaxval1;
        
        //console.log("cminval:" + cminval + " cmaxval:" + cmaxval);
    }
    
    function getPixelColor1(val)
    {
        // === black value, no signal, background noise ===
        // shift the value up, so that cminval is color=0
        val = -val;
        val -= cminval1;
        val -= (hWFrefval1/10);
        
        // === max level, strongest signal ===
        // make the loudest signal to 255
        // shift cmaxval the same factor as the signal value, so both start at 0
        var cmax = (cmaxval1 - cminval1);
        // expand the signal value so that the maxval is 255
        // do this with a log formular, high values are more effected
        var lin = val * factor1 / cmax;
        var revlin = factor1 - lin + 1;
        var mylog = Math.log(revlin);
        var logTo255 = mylog/ minlog1;
        //var val1 = 255 - logTo255;
        var val1 = logTo255;
        
        // low value (<=2) colors reserved for other purposes
        if (val1 <= 2) val1 = 3;
        if (val1 > 250) val1 = 255;

        return val1;
    }
   
    
    function drawWF()
    {
        linethickness = next_linethickness;
        
        // Get the WF canvas and WF context
        var canvas = document.getElementById("wf"); 
        var context = canvas.getContext("2d");
    
        // Get the canvas dimensions
        var width = canvas.width;
        var height = canvas.height;
        
        // create a 1-line canvas for the new data
        var line_cnv = document.getElementById("makeline"); 
        var line_ctx = line_cnv.getContext("2d");
        var line_width = line_cnv.width;
        line_cnv.height = linethickness;
        var imagedata = line_ctx.createImageData(line_width, linethickness);
        // insert the new data into this line
        for( var y=0; y<linethickness; y++)
        {
            calcColorParms(pix, line_width);
            
            var qrg = 0;
            for (var x = 0; x < line_width; x++) 
            {
                var off = y*(line_width*4) + x*4;
                
                // pix[n] is the dBm value of the input signal (positive)
                // it must be converted to the color table index
                var pval = getPixelColor(pix[x]);
                
                // user adjustments
                pval = pval * (Number(wfgain)/50) + (Number(wflevel) -200);
                pval = pval.toFixed(0);
                if(pval < 3) pval = 3;
                if(pval > 255) pval = 255;
                
                if(wfcolor == "red")
                {
                    imagedata.data[off] = 256*(coltab[pval][0]);
                    imagedata.data[off+1] = 256*(coltab[pval][1]);
                    imagedata.data[off+2] = 256*(coltab[pval][2]);
                }
                if(wfcolor == "green")
                {
                    imagedata.data[off] = 256*(coltab[pval][1]);
                    imagedata.data[off+1] = 256*(coltab[pval][0]);
                    imagedata.data[off+2] = 256*(coltab[pval][2]);
                }
                if(wfcolor == "blue")
                {
                    imagedata.data[off] = 256*(coltab[pval][2]);
                    imagedata.data[off+1] = 256*(coltab[pval][1]);
                    imagedata.data[off+2] = 256*(coltab[pval][1]);
                }
                if(wfcolor == "white")
                {
                    imagedata.data[off] = 256*(coltab[pval][0]);
                    imagedata.data[off+1] = 256*(coltab[pval][0]);
                    imagedata.data[off+2] = 256*(coltab[pval][0]);
                }
                imagedata.data[off+3] = 255;
                
                qrg += resolution;
            }
        }
        // and draw it into the 1-line canvas
        line_ctx.putImageData(imagedata, 0, 0);
        
        // now draw the 1-line canvas followed by the old WF canvas
        // this automatically shifts the old picture down one line
        context.drawImage(line_cnv, 0, 0);
        context.drawImage(canvas, 0, linethickness);
    }
    
    function drawOverlay()
    {
        var canvas = document.getElementById("wf_overlay"); 
        var context = canvas.getContext("2d");
    
        // Get the canvas dimensions
        var width = canvas.width;
        var height = canvas.height;
        
        context.clearRect(0,0,width,height);

        var ImageData = context.getImageData(0,0,width,height);

        if(vertmarker == 1)
        {
        
            // draw transparent rectangle at the zoomed qrg range
            for(var i=0; i<height; i++)
            {
                // right part USB
                for(var j=calcpos(end1/2); j<calcpos(end1);j++)
                {
                    ImageData.data[((i*(width*4)) + (j*4) + 0)] = 255;
                    ImageData.data[((i*(width*4)) + (j*4) + 1)] = 255;
                    ImageData.data[((i*(width*4)) + (j*4) + 2)] = 255;
                    ImageData.data[((i*(width*4)) + (j*4) + 3)] = 50;
                }
                
                // left part LSB
                for(var j=calcpos(0); j<calcpos(end1/2);j++)
                {
                    ImageData.data[((i*(width*4)) + (j*4) + 0)] = 255;
                    ImageData.data[((i*(width*4)) + (j*4) + 1)] = 255;
                    ImageData.data[((i*(width*4)) + (j*4) + 2)] = 255;
                    ImageData.data[((i*(width*4)) + (j*4) + 3)] = 20;
                }
            }
            
            // make a vertical line in the middle (which is the carrier frequency)
            j = calcpos(0 + end1/2);
            for(var i=0; i<height; i++)
            {
                ImageData.data[((i*(width*4)) + (j*4) + 0)] = 255;
                ImageData.data[((i*(width*4)) + (j*4) + 1)] = 255;
                ImageData.data[((i*(width*4)) + (j*4) + 2)] = 255;
                ImageData.data[((i*(width*4)) + (j*4) + 3)] = 80;
            }
        }
        
        if(vertlines == 1)
        {
            // draw es'hail-2 vertical lines
            function vl(xpos,i)
            {
                var j = cpos(xpos);
                ImageData.data[((i*(width*4)) + (j*4) + 0)] = 100;
                ImageData.data[((i*(width*4)) + (j*4) + 1)] = 100;
                ImageData.data[((i*(width*4)) + (j*4) + 2)] = 255;
                ImageData.data[((i*(width*4)) + (j*4) + 3)] = 255;
            }
            
            for(var i=0; i<height; i++)
            {
                for(var x=1; x<qo100_bp.length; x++)
                    vl(qo100_bp[x]['from'],i);
            }
        }
        context.putImageData(ImageData,0,0);
    }
    
    function drawOverlay1()
    {
        var canvas = document.getElementById("wf1_overly"); 
        var context = canvas.getContext("2d");
    
        // Get the canvas dimensions
        var width = canvas.width;
        var height = canvas.height;
        
        context.clearRect(0,0,width,height);

        var ImageData = context.getImageData(0,0,width,height);
        
        if(vertlines == 1)
        {
            // draw es'hail-2 vertical lines
            function vl(xpos,i)
            {
                var j = xpos;
                ImageData.data[((i*(width*4)) + (j*4) + 0)] = 100;
                ImageData.data[((i*(width*4)) + (j*4) + 1)] = 100;
                ImageData.data[((i*(width*4)) + (j*4) + 2)] = 255;
                ImageData.data[((i*(width*4)) + (j*4) + 3)] = 255;
            }
            
            function vls(xpos,i)
            {
                var j = xpos;
                ImageData.data[((i*(width*4)) + (j*4) + 0)] = 100;
                ImageData.data[((i*(width*4)) + (j*4) + 1)] = 255;
                ImageData.data[((i*(width*4)) + (j*4) + 2)] = 255;
                ImageData.data[((i*(width*4)) + (j*4) + 3)] = 255;
            }
            
            for(var i=0; i<height; i++)
            {
                // 0 Hz line
                vl(width/2,i);
                
                // SSB lines 200 Hz and Filter-Cutoff line
                if((i%2)==0)
                {
                    //vls(width/2 + 20,i);
                    vls(width/2 + filter_cutoff/10,i);
                    vls(width/2 + filter_cutoff_low/10,i);
                }
            }
        }
        context.putImageData(ImageData,0,0);
    }
    
    function drawWF1()
    {
        linethickness = next_linethickness;
        
        // Get the WF canvas and WF context
        var canvas1 = document.getElementById("wf1"); 
        var context1 = canvas1.getContext("2d");
    
        // Get the canvas dimensions
        var width1 = canvas1.width;
        var height1 = canvas1.height;
        
        // create a 1-line canvas for the new data
        var line_cnv1 = document.getElementById("makeline1"); 
        var line_ctx1 = line_cnv1.getContext("2d");
        var line_width1 = line_cnv1.width;
        line_cnv1.height = linethickness;
        var imagedata1 = line_ctx1.createImageData(line_width1, linethickness);
        // insert the new data into this line
        for( var y=0; y<linethickness; y++)
        {
            calcColorParms1(pix1, line_width1);
            
            var qrg = 0;
            for (var x = 0; x < line_width1; x++) 
            {
                var off = y*(line_width1*4) + x*4;
                
                var pval = getPixelColor1(pix1[x]);
                pval = pval * (Number(wfgain)/50) + (Number(wflevel) - 200);
                pval = pval.toFixed(0);
                if(pval < 3) pval = 3;
                if(pval > 255) pval = 255;
                
                if(wfcolor == "red")
                {
                    imagedata1.data[off] = 256*(coltab[pval][0]);
                    imagedata1.data[off+1] = 256*(coltab[pval][1]);
                    imagedata1.data[off+2] = 256*(coltab[pval][2]);
                }
                if(wfcolor == "green")
                {
                    imagedata1.data[off] = 256*(coltab[pval][1]);
                    imagedata1.data[off+1] = 256*(coltab[pval][0]);
                    imagedata1.data[off+2] = 256*(coltab[pval][2]);
                }
                if(wfcolor == "blue")
                {
                    imagedata1.data[off] = 256*(coltab[pval][2]);
                    imagedata1.data[off+1] = 256*(coltab[pval][1]);
                    imagedata1.data[off+2] = 256*(coltab[pval][1]);
                }
                if(wfcolor == "white")
                {
                    imagedata1.data[off] = 256*(coltab[pval][0]);
                    imagedata1.data[off+1] = 256*(coltab[pval][0]);
                    imagedata1.data[off+2] = 256*(coltab[pval][0]);
                }
                imagedata1.data[off+3] = 255;
                
                qrg += resolution1;
            }
        }
        // and draw it into the 1-line canvas
        line_ctx1.putImageData(imagedata1, 0, 0);
        
        // now draw the 1-line canvas followed by the old WF canvas
        // this automatically shifts the old picture down one line
        context1.drawImage(line_cnv1, 0, 0);
        context1.drawImage(canvas1, 0, linethickness);
    }
    
    // calc px-pos of an offset of WF1 in WF
    function calcpos(x_wf1)
    {
        var A = tuneqrg;
        var B = tuneqrg+end/1000;
        var f = tuneqrg1+(x_wf1-7500)/1000;
        
        var res = 1500 * (f-A)/(B-A);
        
        return Math.floor(res);
    }
    
    function cpos(x)
    {
        var A = tuneqrg;
        var B = tuneqrg+end/1000;
        
        var res = 1500 * (x-A)/(B-A);
        
        return Math.floor(res);
    }
    

    
    function drawConnect()
    {
        var canvas = document.getElementById("connect"); 
        var context = canvas.getContext("2d");
    
        // Get the canvas dimensions
        var width = canvas.width;
        var height = canvas.height;
        
        context.fillStyle = '#ffffff';
        context.fillRect(0,0,width,height);
        
        context.strokeStyle = '#000000';
        context.fillStyle = '#8080ff';
        
        context.beginPath();
        
        context.moveTo(0, height-1);
        context.lineTo(calcpos(0), 1);
        context.lineTo(calcpos(end1), 1);
        context.lineTo(width-1, height-1);

        context.closePath();
        context.fill();
        
        context.stroke();
    }

    function get32(myarr)
    {
        var val = myarr[0];
        val <<= 8;
        val += myarr[1];
        val <<= 8;
        val += myarr[2];
        val <<= 8;
        val += myarr[3];
        return val;
    }

    function get16(myarr)
    {
        var val = myarr[0];
        val <<= 8;
        val += myarr[1];
        return val;
    }
    
    function get8(myarr)
    {
        var val = myarr[0];
        return val;
    }
    
    var ftx = 8089500;
    var rflock = 0;

    // arr is an uint8 array containing the pixel data at arr+20
    // each value is 16 bit, MSB first, so in total 3000 bytes = 1500 fft values
    
    var rxpix = new Array();
    var rxpix1 = new Array();
    var log1122 = Math.log(1.122);
    var db_maxval = Math.log(32768.0*1500) / log1122;
    
    function val_to_dbm(v)
    {
        if (v < 1) v = 1;    
        var dval = Math.log(v) / log1122;
        dval = dval - db_maxval;
        return -dval;
    }
    
    // raw data are in rxpix. It contains 9000 values for a range of 900kHz
    // fill "pix" with data for the display. pix should contain 1500 values
    // beginning at startpos until endpos
    // also: convert rxpix into dBm before filling it into pix
    function scaledBm(startpos, endpos)
    {
        // calculate the start and end offset in rxpix for requested frequencies
        // tuneqrg ... qrg of left margin of rxpix
        var start_offset_qrg = startpos - tuneqrg*1000;         // frequency offset
        var start_offset_bins = start_offset_qrg / resolution;  // index into rxpix
        
        var end_offset_qrg = endpos - tuneqrg*1000;             // frequency offset
        var end_offset_bins = end_offset_qrg / resolution;      // index into rxpix
        
        // calculate step size to divide above range into 1500 pixels
        var picsteps = (end_offset_bins - start_offset_bins);
        var stepsize = picsteps / 1500;
        
        // generate the values for the pictures
        var idx = start_offset_bins;
        for(var i=0; i<1500; i++)
        {
            // stepsize may be a float number, so idx is also float
            // access rxpix's index at the nearest integer of idx
            var idx_int = Math.round(idx);
            // reconstruct the fft value, take the highest one in this span
            var fftval = -9999;
            for(var s=0; s<Math.round(stepsize); s++)
            {
                var v = rxpix[(idx_int+s)*2];
                v <<= 8;
                v += rxpix[(idx_int+s)*2+1];
                if(v > fftval) fftval = v;
            }
            
            idx += stepsize;
            
            // calculate the dBm value
            pix[i] = val_to_dbm(fftval);
        }
    }
    
    function scaledBm1()
    {
        var idx = 0;
        for(var i=0; i<1500; i++)
        {
            // reconstruct the fft value
            var fftval = rxpix1[idx++];
            fftval <<= 8;
            fftval += rxpix1[idx++];
            
            // calculate the dBm value
            pix1[i] = val_to_dbm(fftval);
        }
    }


    var outband_noise = 0;  // level of unused frequency
    var basenoiselevel = 0; // level of transponder noise
    var trsmoothing = 25;
    var trnoisesmoothing = 50;
    var trnoisearr = new Array(50); // smooth transponder noise
    var basenoisearr = new Array(25);
    var tridx = 0;
    var trfirst = 1;
    
    function getRXpixIdx(qrg)
    {
        // index in rxpix according this fomular
        // idx = 9000*(qrg-10489,25)/(10490,15-10489,25)
        return Math.round(9000*(qrg-10489.25)/(10490.15-10489.25));
    }
    
    function measure_noise_level(sqrg, eqrg)
    {
        // measure the average noise level of range and ignore existing signals
        var rs = getRXpixIdx(sqrg);
        var re = getRXpixIdx(eqrg);
        var rlen = re-rs;
        var rarr = new Array(rlen+10);
        
        // read values from rxpix into a dBm array
        var idx  = 0;
        for(var i=rs*2; i<re*2; i+=2)
        {
            var v = rxpix[i];
            v <<= 8;
            v += rxpix[i+1];
            
            rarr[idx++] = -val_to_dbm(v);
        }
        
        // calc average value of this range
        var av1=0;
        for(var i=0; i<rlen; i++)
            av1 += rarr[i];
        av1 /= rlen;
        
        // mark values as invalid if 5dB higher than average (remove existing carriers)
        for(var i=0; i<rlen; i++)
        {
            if(rarr[i] > (av1+5))
                rarr[i] = -9999;
        }
        
        // calc average value of the cleaned range again
        var av2=0;
        var cnt = 0;
        for(var i=0; i<rlen; i++)
        {
            if(rarr[i] != -9999)
            {
                av2 += rarr[i];
                cnt++;
            }
        }
        av2 /= cnt;
        
        return av2;
    }
    
    function get_noiselevels()
    {
        var v1 = measure_noise_level(10489.753,10489.757);
        var v2 = measure_noise_level(10489.748,10489.752);
        basenoiselevel = (v1+v2)/2;
        
        // smooth
        if(trfirst == 1)
        {
            for(var i=0; i<trsmoothing; i++)
                basenoisearr[i] = diff;
        }
        else
        {
            for(var i=0; i<(trsmoothing-1); i++)
                basenoisearr[i] = basenoisearr[i+1];
            basenoisearr[trsmoothing-1] = basenoiselevel;
        }
        
        basenoiselevel = 0;
        for(var i=0; i<trsmoothing; i++)
            basenoiselevel += basenoisearr[i];
        basenoiselevel /= trsmoothing;

        // correction:
        // at ,300 the transponder noise is already there with about 0,2 dB
        // also the SDR generates additional 0,2dB because its so close to its tuning frequency
        // substract this as correction
        outband_noise = measure_noise_level(10489.270,10489.310) - 0.4;
        
        // difference between outband and transponder noise
        var diff = basenoiselevel - outband_noise;
        
        if(diff > 20) return;
        
        // smooth
        if(trfirst == 1)
        {
            trfirst = 0;
            for(var i=0; i<trnoisesmoothing; i++)
                trnoisearr[i] = diff;
        }
        else
        {
            for(var i=0; i<(trnoisesmoothing-1); i++)
                trnoisearr[i] = trnoisearr[i+1];
            trnoisearr[trnoisesmoothing-1] = diff;
        }
        
        diff = 0;
        for(var i=0; i<trnoisesmoothing; i++)
            diff += trnoisearr[i];
        diff /= trnoisesmoothing;
        
        $("#transpondernoise").html("Transponder Noise:" + diff.toFixed(1) + "dB");
    }
    
    var bandplan = 1;
    var qo100_bp = qo100_bp_2;
    var startqrg = 10489250000;
    var endqrg   = 10490150000;
    var fusers = 0;
    var trxavail = 0;
    
    // process data received via the web socket
    function handle_type(type,arr,pos,len)
    {
        switch (bandplan)
        {
        case 0: // full spektrum
                startqrg = 10489250000;
                endqrg   = 10490150000;
                break;
        case 1: // new bandplan
                startqrg = 10489475000;
                endqrg   = 10490025000;
                break;
        }
        
        if(type == 0 && reload)
        {
            var idx = pos+2;
            rflock = arr[pos];
            tunerqrg = get32(arr.slice(idx));       // real qrg of the SDR's hardware tuner in Hz
            end = get32(arr.slice(idx+4));          // width of the spectrum data in Hz
            tuneqrg = get32(arr.slice(idx+8));      // QRG of the left margin of the picture in kHz
            resolution = get32(arr.slice(idx+12));  // Hz per pixel
            foffset = get32(arr.slice(idx+16));     // offset of the RX (lower WF) freq to the SDR tuned freq
            fusers = get8(arr.slice(idx+20));       // number of logged in users
            trxavail = get8(arr.slice(idx+21));     // 1 if TRX is connected via CAT
            var fspare2 = get8(arr.slice(idx+22));
            var fspare3 = get8(arr.slice(idx+23));
            rxpix  = arr.slice(idx+24);
            pixwidth = end / resolution;            // number of fft values in rxpix

            var fval_Hz = tuneqrg*1000 + foffset;
            var fval = (tuneqrg + foffset/1000)/1e3;
            var sd = "RX: " + fval.toFixed(6) + " MHz, TX: " + (fval-ftx/1000).toFixed(6) + " MHz";
            $("#tuneinfo").html(sd);
            
            $("#loggedinusers").html("Users:" + fusers);
            if(trxavail)
                document.getElementById("alive_icon").src = "green.png";
            else
                document.getElementById("alive_icon").src = "red.png";

            //console.log("old:"+tunerqrg+" "+end+" "+tuneqrg+" "+resolution+" "+foffset);
            
            // select values between start and end and scale dBm
            scaledBm(startqrg, endqrg);
            scaledBm1(fval_Hz-7500, fval_Hz+7500);
            // change values to match new qrg margins
            end = endqrg - startqrg;
            tuneqrg = startqrg / 1000;
            resolution = end / 1500;
            
            get_noiselevels();
            
            drawWFtitle_eshail_rx("wf_title");
            drawWFtitle_eshail_tx("wf_txtitle");
            drawWFtitle_eshail_ssb("wf_title1",fval);
            drawWFtitle_eshail_ssb("wf_txtit1",fval-ftx/1000);
            drawMODEtitle("wf_mdtitle",tuneqrg,tuneqrg+end/1000,resolution);
            drawWF();
            draw_spec();
            drawOverlay();
            reload = 0;
        }
        
        if(type == 1)
        {
            var idx = pos + 2;
            tunerqrg = get32(arr.slice(idx));
            end1 = get32(arr.slice(idx+4));
            tuneqrg1 = get32(arr.slice(idx+8));
            resolution1 = get32(arr.slice(idx+12));
            foffset1 = get32(arr.slice(idx+16));
            rxpix1  = arr.slice(idx+20);
            scaledBm1();
            pixwidth1 = end1 / resolution1;
            
            drawWF1();
            drawConnect();
            drawOverlay1();
        }

        if(type == 2)
        {
            if(playON == 1)
            {
                player.feed(arr.slice(pos,pos+len));
            }
        }
        
        if(type == 3)
        {
            // config data
            var idx = pos+2;
            extract_data(arr,idx);
            $('#setupbutton').show();
        }
    }
    
    function showLock()
    {
        lk = "sync-ing ............";
        if(rflock == 1) lk = "LOCKED to beacon";
        $("#lockstat").html(lk);
    }
    
    // send initial values to the server as soon as the websocket is open
    var openintv;
    var openints = 0;
    function nowOpen()
    {
        var clear = 0;
        
        switch(openints)
        {
            case 0: websocket.send("ssbmode:1\0"); 
                    break;
            case 1: websocket.send("getconf:1\0"); 
                    break;
            case 2: var ckb = $("#catonoff").is(':checked');
                    if(ckb == true)
                        websocket.send("catonof:1\0");
                    else
                        websocket.send("catonof:0\0");
                    break;
            default:document.title = config.call + "'s QO100 WebSDR";
                    clear = 1;
                    break;
        }
        
        openints++;
        if(clear == 1)
            clearInterval(openintv);
    }

    function openWebSocket()
    {
        window.WebSocket = window.WebSocket || window.MozWebSocket;

        // get socket URL fromactual browser-URL
        var x = location.href;
        var a = x.split("/");
        var y = "?";
        for(var i=0; i<a.length; i++)
        {
            if(a[i].length > 6)
            {
                y = a[i];
                break;
            }
        }
        var ya = y.split(":");
        sockurl = "ws://" + ya[0] + ":8091";
        //console.log(sockurl);
        //$('#myinfo').html(sockurl);
        websocket = new WebSocket(sockurl);
        websocket.binaryType = "arraybuffer";

        websocket.onopen = function () {
            sockOpen = 1;
            $("#status").html(sockurl);
            //alert("WebSocket is now OPEN");
            openints = 0;
            openintv = setInterval(nowOpen, 500);
        };

        websocket.onerror = function () {
            $("#status").html("Error ... reconnecting ...");
            websocketclose();
            sockOpen = 0;
        };
        
        websocket.onclose = function () {
            $("#status").html("Disconnected ... connecting ...");
            websocketclose();
            sockOpen = 0;
        };

        websocket.onmessage = function (message) 
        {
            var arr = new Uint8Array(message.data);
            // handle first element
            var pos = 0;
            var type = arr[pos++];
            var len = (arr[pos++] << 8);
            len += arr[pos++];
            
            handle_type(type,arr,pos,len);
            pos += len;

            if(arr.length > pos)
            {
                // handle second element
                var type = arr[pos++];
                var len = (arr[pos++] << 8);
                len += arr[pos++];
                handle_type(type,arr,pos,len);
                pos += len;
            }

            if(arr.length > pos)
            {
                // handle 3rd element
                var type = arr[pos++];
                var len = (arr[pos++] << 8);
                len += arr[pos++];
                handle_type(type,arr,pos,len);
            }
            // remark: window.requestAnimationFrame(drawWF); do NOT use this, high CPU load and corrupts picture if in background
        };
        
        // store settings and send to server
        
        $('#color').click(function(e) {
            e.preventDefault();
            wfcolor = $(this).children("option:selected").val();
            localStorage.setItem('color',wfcolor);
        });
        
        $('#speed').click(function(e) {
            e.preventDefault();
            clearInterval(intv);
            interval = $(this).children("option:selected").val();
            intv = setInterval(reloadData, interval);
            localStorage.setItem('speed',interval);
        });
        
        $('#yzoom').click(function(e) {
            e.preventDefault();
            next_linethickness = Number($(this).children("option:selected").val());
            localStorage.setItem('yzoom',next_linethickness);
        });

        $('#yline').click(function(e) {
            e.preventDefault();
            vertlines = Number($(this).children("option:selected").val());
            localStorage.setItem('ylines',vertlines);
        });

        $('#ymarker').click(function(e) {
            e.preventDefault();
            vertmarker = Number($(this).children("option:selected").val());
            localStorage.setItem('ymarker',vertmarker);
        });
        
        // mouse wheel in QRG text input field
        $(document).ready(function(){
        $('#qrg').bind('mousewheel', function(e){
                if($("#sync").is(':checked') == false)
                {
                    $('#qrg').blur();
                    if(e.originalEvent.wheelDelta > 0) {
                        websocket.send("tunerfr:1\0");
                    }
                    else{
                        websocket.send("tunerfr:0\0");
                    }
                }
            });
        });
        
        // ENTER key in QRG text input field
        $('#qrg').keyup(function(event){
            var keycode = (event.keyCode ? event.keyCode : event.which);
            if(keycode == '13'){
                var qrgval = $('#qrg').val();
                websocket.send("tunervl:" + qrgval + "\0");
            }

        });
        
    }
    
    function CATonoff()
    {
        var ckb = $("#catonoff").is(':checked');
        if(ckb == true)
            websocket.send("catonof:1\0");
        else
            websocket.send("catonof:0\0");
    }
    
    var trace_mode = 0; // 0=average, 1=peak
    
    function clickAverage()
    {
        var ckb = $("#average").is(':checked');
        if(ckb && trace_mode == 1)
        {
            document.getElementById("peak").checked = false;
            trace_mode = 0;
            for(var x=0; x<(document.getElementById("spec").width); x++)
            {
                peakarr[x] = -200;
            }
        }
    }
    
    function clickPeak()
    {
        var ckb = $("#peak").is(':checked');
        if(ckb && trace_mode == 0)
        {
            document.getElementById("average").checked = false;
            trace_mode = 1;
        }
    }
    
    function clickSelBand(b)
    {
        bandplan = b;
        
        document.getElementById("full").checked = false;
        document.getElementById("new_band").checked = false;
        
        if(b == 0) 
        {
            document.getElementById("full").checked = true;
            qo100_bp = qo100_bp_2;
        }
        
        if(b == 1) 
        {
            document.getElementById("new_band").checked = true;
            qo100_bp = qo100_bp_2;
        }
        
        // clean WF
        var canvas = document.getElementById("wf"); 
        var context = canvas.getContext("2d");
        context.clearRect(0, 0, canvas.width, canvas.height);
        
        // store setting
        localStorage.setItem('bandplan',bandplan);
    }
    
    function setListboxes()
    {
        var v = localStorage.getItem('sync');
        if(v != null)
        {
            $('#sync').attr('checked', v=="1");
        }
        
        var v = localStorage.getItem('bandplan');
        if(v != null)
        {
            clickSelBand(parseInt(v));
        }
        
        var v = localStorage.getItem('color');
        if(v != null)
        {
            wfcolor = v;
            $('#color').val(wfcolor);
        }
            
        v = localStorage.getItem('speed');
        if(v != null)
        {
            interval = v;
            clearInterval(intv);
            intv = setInterval(reloadData, interval);
            $('#speed').val(interval);
        }
        else
        {
            intv = setInterval(reloadData, 10);
        }
            
        v = localStorage.getItem('yzoom');
        if(v != null)
        {
            next_linethickness = v;
            $('#yzoom').val(next_linethickness);
        }
            
        v = localStorage.getItem('ylines');
        if(v != null)
        {
            vertlines = v;
            $('#yline').val(vertlines);
        }
            
        v = localStorage.getItem('ymarker');
        if(v != null)
        {
            vertmarker = v;
            $('#ymarker').val(vertmarker);
        }

        var slider_specmid = document.getElementById("specmid");
        v = localStorage.getItem('specmid');
        if(v != null)
        {
            specarrlen = v;
            slider_specmid.value = specarrlen;
        }
        
        slider_specmid.oninput = function() 
        {
            specarrlen = this.value;
            if(specarrlen <= 0) specarrlen = 1;
            localStorage.setItem('specmid',specarrlen);
        }
        
        var slider_speclevel = document.getElementById("speclevel");
        v = localStorage.getItem('speclevel');
        if(v != null)
        {
            speclevel = v;
            slider_speclevel.value = speclevel;
        }
            
        slider_speclevel.oninput = function() 
        {
            speclevel = this.value;
            if(Number(speclevel) > Number(specgain)) specgain = Number(speclevel) + 10.0;
            localStorage.setItem('speclevel',speclevel);
            
        }
        
        var slider_specgain = document.getElementById("specgain");
        v = localStorage.getItem('specgain');
        if(v != null)
        {
            specgain = v;
            slider_specgain.value = specgain;
        }
            
        slider_specgain.oninput = function() 
        {
            specgain = this.value;
            if(Number(specgain) < Number(speclevel)) speclevel = Number(specgain) - 10.0;
            localStorage.setItem('specgain',specgain);
        }
        
        var slider_audiogain = document.getElementById("audiogain");
        v = localStorage.getItem('audiogain');
        if(v != null)
        {
            player.volume(v);
            slider_audiogain.value = audiogain;
        }
            
        slider_audiogain.oninput = function() 
        {
            player.volume(this.value);
            localStorage.setItem('audiogain',audiogain);
        }
        
        var checkbox_autolevel = document.getElementById("spekautolevel");
        var v1x = localStorage.getItem('spekautolevelx');
        if(v1x != null)
        {
            if(v1x == 0)
                checkbox_autolevel.checked = false;
            else
                checkbox_autolevel.checked = true;
        }
        else
        {
            // ON as default
            checkbox_autolevel.checked = true;
        }
        
        checkbox_autolevel.onclick = function() 
        {
            autoclk = this.checked;
            if(this.checked)
                localStorage.setItem('spekautolevelx',1);
            else
                localStorage.setItem('spekautolevelx',0);
        }
        
        var slider_wflevel = document.getElementById("wflevel");
        v = localStorage.getItem('wflevel');
        if(v != null)
        {
            wflevel = v;
            slider_wflevel.value = wflevel;
        }
            
        slider_wflevel.oninput = function() 
        {
            wflevel = this.value;
            
            localStorage.setItem('wflevel',wflevel);
        }
        
        var slider_wfgain = document.getElementById("wfgain");
        v = localStorage.getItem('wfgain');
        if(v != null)
        {
            wfgain = v;
            slider_wfgain.value = wfgain;
        }
            
        slider_wfgain.oninput = function() 
        {
            wfgain = this.value;
            //console.log(wfgain);
            localStorage.setItem('wfgain',wfgain);
        }
        
        var slider_audiolowfilter = document.getElementById("lowfilter");
        v = localStorage.getItem('lowfilter');
        if(v != null)
        {
            filter_cutoff = v;
            player.setHPfilter(v);
            slider_audiolowfilter.value = filter_cutoff;
            showFilterMargins();
        }
            
        slider_audiolowfilter.oninput = function() 
        {
            filter_cutoff = this.value;
            player.setHPfilter(filter_cutoff);
            localStorage.setItem('lowfilter',filter_cutoff);
            showFilterMargins();
        }

        var slider_audiohighfilter = document.getElementById("highfilter");
        v = localStorage.getItem('highfilter');
        if(v != null)
        {
            filter_cutoff_low = v;
            player.setLPfilter(v);
            slider_audiohighfilter.value = filter_cutoff_low;
            showFilterMargins();
        }
            
        slider_audiohighfilter.oninput = function() 
        {
            filter_cutoff_low = this.value;
            player.setLPfilter(filter_cutoff_low);
            localStorage.setItem('highfilter',filter_cutoff_low);
            showFilterMargins();
        }
}

function showFilterMargins()
{
    var flab = "Filter: " + filter_cutoff + " Hz ... " + filter_cutoff_low + " Hz";
    document.getElementById("filterlabel").innerHTML = flab;
}

var setupwindow;

function setup()
{
    // open if setup not exists
    if(setupwindow == undefined || setupwindow == null)
    {
        setupwindow = window.open("sdrsetup.html?"+ (new Date()).getTime(),"setupwindow");
        var timer = setInterval(function() {
            if(setupwindow.closed) 
            {
                clearInterval(timer);
                sendSetup();
                setupwindow = null;
            }
        },500);
        return;
    }
        
    // focus setup if exists
    if(setupwindow.closed)
        setupwindow = window.open("sdrsetup.html","setupwindow");
}

function closingCode()
{
    setupwindow.close();
    return null;
}

</script>
</head>
        
    <body>
        <div class="header_mytitle" id="header_mytitle">QO-100 on es'hail-2 NB-Transponder SSB Live Stream</div>
        <div class="header_subtitle" id="header_subtitle">PlutoSDR Alpha test (F5OEO) based on DJ0ABR V2.81</div>
        <div class="controls">
            <div class="leftside">
                <select id="color" class="selectbox" name="color">
                    <option value="red">red</option>
                    <option value="green">green</option>
                    <option value="blue">blue</option>
                    <option value="white">white</option>
                </select>
                <select id="speed" class="selectbox" name="speed">
                    <option value="10">full speed</option>
                    <option value="200">200ms</option>
                    <option value="300">300ms</option>
                    <option value="500">500ms</option>
                    <option value="750">750ms</option>
                    <option value="1000">1s</option>
                    <option value="2000">2s</option>
                    <option value="4000">4s</option>
                </select>
                <select id="yzoom" class="selectbox" name="yzoom">
                    <option value="1">1 pixel/line</option>
                    <option value="2">2 pixel/line</option>
                    <option value="3">3 pixel/line</option>
                    <option value="4">4 pixel/line</option>
                    <option value="5">5 pixel/line</option>
                    <option value="6">6 pixel/line</option>
                    <option value="7">7 pixel/line</option>
                    <option value="8">8 pixel/line</option>
                    <option value="9">9 pixel/line</option>
                    <option value="10">10 pixel/line</option>
                </select>
                <select id="yline" class="selectbox" name="yline">
                    <option value="1">QRG line ON</option>
                    <option value="0">QRG line OFF</option>
                </select>
                <select id="ymarker" class="selectbox" name="ymarker">
                    <option value="1">Marker ON</option>
                    <option value="0">Marker OFF</option>
                </select>
                
                <div class="filtercontainer">
                <button class="mybutton" id="audiobutton" onclick="audioON()">AUDIO OFF</button>
                <label class="audiolabel">Filter Low:</label>
                <input type ="range" min="50" max="700" step="10" id="lowfilter" value="200" class="audio_filter_slider" />

                <label class="audiolabel">High:</label>
                <input type ="range" min="800" max="3600" step="50" id="highfilter" value="2400" class="audio_filter_slider" />
                </div>
                
                <div class="controls_left">
                    <div class="controls_left_left">
                        <div>QRG-AUTOLOCK:</div>
                        <div>SDR-SERVER:</div>
                    </div>
                    <div class="controls_left_mid">
                        <div id="lockstat">Lockstatus</div>
                        <div id="status">Status</div>
                    </div>
                    <div class="controls_left_right">
                        <div>
                            <label id="filterlabel"></label>
                        </div>
                    </div>
                </div>
                <div>
                .
                </div>
                <div class="leftrow_menuline">
                    <div style="width: 210px;">
                        bandwidth:
                    </div>
                    <div style="width: 140px;">
                        <input type="checkbox" id="full" onclick="clickSelBand(0)">Full
                    </div>
                    <div style="width: 140px;">
                        <input type="checkbox" id="new_band" onclick="clickSelBand(1)">Bandplan
                    </div>
                </div>
            </div>
            
            <div class="rl_space">
            </div>
            <div class="midside">
                <div class="rightrow">
                    <div class="rightcell_label_header" style="display: flex;">
                        <div style="width:150px;">
                            Spectrum:
                        </div>
                        <div style="width:100px;font-size:14px;font-weight: 300;">
                            <label for="spekautolevel">
                                Auto:<input type="checkbox" id="spekautolevel">
                            </label>
                        </div>
                    </div>
                    <div class="rightcell_slider">
                    </div>
                    <div class="rightcell_label_header">
                        Waterfall:
                    </div>
                    <div class="rightcell_slider">
                    </div>
                </div>
                <div class="rightrow_space">
                </div>
                <div class="rightrow">
                    <div class="rightcell_label">
                        Level:
                    </div>
                    <div class="rightcell_slider">
                        <input id="speclevel" type="range" min="-180" max="-20" value="-180" style="direction: rtl;">                        
                    </div>
                    <div class="rightcell_label">
                        Level:
                    </div>
                    <div class="rightcell_slider">
                        <input id="wflevel" type="range" min="50" max="350" value="230">
                    </div>
                </div>
                <div class="rightrow">
                    <div class="rightcell_label">
                        Gain:
                    </div>
                    <div class="rightcell_slider">
                        <input id="specgain" type="range" min="-100" max="0" value="-60" style="direction: rtl;">
                    </div>
                    <div class="rightcell_label">
                        Gain:
                    </div>
                    <div class="rightcell_slider">
                        <input id="wfgain" type="range" min="20" max="100" value="50">
                    </div>
                </div>
                <div class="rightrow">
                    <div class="rightcell_label">
                        Average:
                    </div>
                    <div class="rightcell_slider">
                        <input id="specmid" type="range" min="1" max="20" value="10">
                    </div>
                    <div class="rightcell_label">
                        Audio:
                    </div>
                    <div class="rightcell_slider">
                        <input id="audiogain" type="range" min="1" max="100" value="1">
                    </div>
                </div>
                <div class="midrow_menuline" style="background-color: #e0e0e0;">
                    <div style="width: 230px;">
                        spectrum trace mode:
                    </div>
                    <div style="width: 150px;">
                        <input type="checkbox" id="average" onclick="clickAverage()" checked>Average
                    </div>
                    <div style="width: 180px;">
                        <input type="checkbox" id="peak" onclick="clickPeak()">Peak Hold
                    </div>
                </div>
            </div>
            
            <div class="rl_space">
            </div>
            
            <div class="rightside">
                <div class="myflex">
                    <div>
                        <input type="checkbox" id="catonoff" onclick="CATonoff()">Icom CAT
                        
                    </div>
                    <div>
                        <img id="alive_icon" style="margin-top:2px; margin-left:2px">
                    </div>
                    <div style="position:absolute; right:0">
                        <button class="mybutton" id="setupbutton" onclick="setup()" style="display:none" align="right">SETUP</button>
                    </div>
                </div>
                <div class="cat_message">
                ! for local users only !<hr>
                </div>
                <div id="mousepos" class="measurements"></div>
                <div id="transpondernoise" class="measurements"></div>
                <div id="loggedinusers" class="measurements"></div>
                <div class="rightrow_menuline">
                </div>
            </div>
        </div>
        
        <div id="tuneinfo" class="tuneinfo">Tuner</div>
        <br>
        
        <div style="position: relative;">
            <canvas id="wf_title"   width="1500" height="20"  style="position: absolute; left: 0; top: 0px; z-index: 0;"></canvas>
            <canvas id="wf_txtitle" width="1500" height="20"  style="position: absolute; left: 0; top: 20px; z-index: 0;"></canvas>
            <canvas id="spec"       width="1500" height="150" style="position: absolute; left: 0; top: 40px; z-index: 0;"></canvas>
            <canvas id="wf_mdtitle" width="1500" height="20"  style="position: absolute; left: 0; top: 190px; z-index: 0;"></canvas>
            <canvas id="wf_overlay" width="1500" height="300" style="position: absolute; left: 0; top: 210px; z-index: 1;"></canvas>
            <canvas id="wf"         width="1500" height="300" style="position: absolute; left: 0; top: 210px; z-index: 0;"></canvas>
            <canvas id="connect"    width="1500" height="30"  style="position: absolute; left: 0; top: 510px; z-index: 0;"></canvas>
            <canvas id="wf_title1"  width="1500" height="20"  style="position: absolute; left: 0; top: 540px; z-index: 0;"></canvas>
            <canvas id="wf_txtit1"  width="1500" height="20"  style="position: absolute; left: 0; top: 560px; z-index: 0;"></canvas>
            <canvas id="wf1_overly" width="1500" height="150" style="position: absolute; left: 0; top: 581px; z-index: 1;"></canvas>
            <canvas id="wf1"        width="1500" height="150" style="position: absolute; left: 0; top: 581px; z-index: 0;"></canvas>
            <canvas id="makeline"   width="1500" height="10"  style="display:none"></canvas>
            <canvas id="makeline1"  width="1500" height="10"  style="display:none"></canvas>
        </div>
    </body>
</html>

